1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 package sun.tools.jconsole;
27
28 import java.awt.*;
29 import java.awt.event.*;
30 import java.io.*;
31 import java.lang.management.*;
32 import java.lang.reflect.*;
33
34 import javax.swing.*;
35 import javax.swing.border.*;
36 import javax.swing.event.*;
37
38 import java.util.*;
39 import java.util.concurrent.*;
40 import java.util.List;
41
42 import sun.awt.*;
43
44 import static sun.tools.jconsole.OverviewPanel.*;
45 import static sun.tools.jconsole.Resources.*;
46 import static sun.tools.jconsole.Utilities.*;
47
48
49 @SuppressWarnings("serial")
50 class ThreadTab extends Tab implements ActionListener, DocumentListener, ListSelectionListener {
51 PlotterPanel threadMeter;
52 TimeComboBox timeComboBox;
53 JTabbedPane threadListTabbedPane;
54 DefaultListModel listModel;
55 JTextField filterTF;
56 JLabel messageLabel;
57 JSplitPane threadsSplitPane;
58 HashMap<Long, String> nameCache = new HashMap<Long, String>();
59
60 private ThreadOverviewPanel overviewPanel;
61 private boolean plotterListening = false;
62
63
64 private static final String threadCountKey = "threadCount";
65 private static final String peakKey = "peak";
66
67 private static final String threadCountName = Resources.getText("Live Threads");
68 private static final String peakName = Resources.getText("Peak");
69
70 private static final Color threadCountColor = Plotter.defaultColor;
71 private static final Color peakColor = Color.red;
72
73 private static final Border thinEmptyBorder = new EmptyBorder(2, 2, 2, 2);
74
75 private static final String infoLabelFormat = "ThreadTab.infoLabelFormat";
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95 public static String getTabName() {
96 return Resources.getText("Threads");
97 }
98
99 public ThreadTab(VMPanel vmPanel) {
100 super(vmPanel, getTabName());
101
102 setLayout(new BorderLayout(0, 0));
103 setBorder(new EmptyBorder(4, 4, 3, 4));
104
105 JPanel topPanel = new JPanel(new BorderLayout());
106 JPanel plotterPanel = new JPanel(new VariableGridLayout(0, 1, 4, 4, true, true));
107
108 add(topPanel, BorderLayout.NORTH);
109 add(plotterPanel, BorderLayout.CENTER);
110
111 JPanel controlPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 5));
112 topPanel.add(controlPanel, BorderLayout.CENTER);
113
114 threadMeter = new PlotterPanel(Resources.getText("Number of Threads"),
115 Plotter.Unit.NONE, true);
116 threadMeter.plotter.createSequence(threadCountKey, threadCountName, threadCountColor, true);
117 threadMeter.plotter.createSequence(peakKey, peakName, peakColor, true);
118 setAccessibleName(threadMeter.plotter,
119 getText("ThreadTab.threadPlotter.accessibleName"));
120
121 plotterPanel.add(threadMeter);
122
123 timeComboBox = new TimeComboBox(threadMeter.plotter);
124 controlPanel.add(new LabeledComponent(Resources.getText("Time Range:"),
125 getMnemonicInt("Time Range:"),
126 timeComboBox));
127
128 listModel = new DefaultListModel();
129
130 JTextArea textArea = new JTextArea();
131 textArea.setBorder(thinEmptyBorder);
132 textArea.setEditable(false);
133 setAccessibleName(textArea,
134 getText("ThreadTab.threadInfo.accessibleName"));
135 JList list = new ThreadJList(listModel, textArea);
136
137 Dimension di = new Dimension(super.getPreferredSize());
138 di.width = Math.min(di.width, 200);
139
140 JScrollPane threadlistSP = new JScrollPane(list);
141 threadlistSP.setPreferredSize(di);
142 threadlistSP.setBorder(null);
143
144 JScrollPane textAreaSP = new JScrollPane(textArea);
145 textAreaSP.setBorder(null);
146
147 threadListTabbedPane = new JTabbedPane(JTabbedPane.TOP);
148 threadsSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
149 threadlistSP, textAreaSP);
150 threadsSplitPane.setOneTouchExpandable(true);
151 threadsSplitPane.setBorder(null);
152
153 JPanel firstTabPanel = new JPanel(new BorderLayout());
154 firstTabPanel.setOpaque(false);
155
156 JPanel firstTabToolPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 2));
157 firstTabToolPanel.setOpaque(false);
158
159 filterTF = new PromptingTextField("Filter", 20);
160 filterTF.getDocument().addDocumentListener(this);
161 firstTabToolPanel.add(filterTF);
162
163 JSeparator separator = new JSeparator(JSeparator.VERTICAL);
164 separator.setPreferredSize(new Dimension(separator.getPreferredSize().width,
165 filterTF.getPreferredSize().height));
166 firstTabToolPanel.add(separator);
167
168 JButton detectDeadlockButton = new JButton(Resources.getText("Detect Deadlock"));
169 detectDeadlockButton.setMnemonic(getMnemonicInt("Detect Deadlock"));
170 detectDeadlockButton.setActionCommand("detectDeadlock");
171 detectDeadlockButton.addActionListener(this);
172 detectDeadlockButton.setToolTipText(getText("Detect Deadlock.toolTip"));
173 firstTabToolPanel.add(detectDeadlockButton);
174
175 messageLabel = new JLabel();
176 firstTabToolPanel.add(messageLabel);
177
178 firstTabPanel.add(threadsSplitPane, BorderLayout.CENTER);
179 firstTabPanel.add(firstTabToolPanel, BorderLayout.SOUTH);
180 threadListTabbedPane.addTab(Resources.getText("Threads"), firstTabPanel);
181
182 plotterPanel.add(threadListTabbedPane);
183 }
184
185 private long oldThreads[] = new long[0];
186
187 public SwingWorker<?, ?> newSwingWorker() {
188 final ProxyClient proxyClient = vmPanel.getProxyClient();
189
190 if (!plotterListening) {
191 proxyClient.addWeakPropertyChangeListener(threadMeter.plotter);
192 plotterListening = true;
193 }
194
195 return new SwingWorker<Boolean, Object>() {
196 private int tlCount;
197 private int tpCount;
198 private long ttCount;
199 private long[] threads;
200 private long timeStamp;
201
202 public Boolean doInBackground() {
203 try {
204 ThreadMXBean threadMBean = proxyClient.getThreadMXBean();
205
206 tlCount = threadMBean.getThreadCount();
207 tpCount = threadMBean.getPeakThreadCount();
208 if (overviewPanel != null) {
209 ttCount = threadMBean.getTotalStartedThreadCount();
210 } else {
211 ttCount = 0L;
212 }
213
214 threads = threadMBean.getAllThreadIds();
215 for (long newThread : threads) {
216 if (nameCache.get(newThread) == null) {
217 ThreadInfo ti = threadMBean.getThreadInfo(newThread);
218 if (ti != null) {
219 String name = ti.getThreadName();
220 if (name != null) {
221 nameCache.put(newThread, name);
222 }
223 }
224 }
225 }
226 timeStamp = System.currentTimeMillis();
227 return true;
228 } catch (IOException e) {
229 return false;
230 } catch (UndeclaredThrowableException e) {
231 return false;
232 }
233 }
234
235 protected void done() {
236 try {
237 if (!get()) {
238 return;
239 }
240 } catch (InterruptedException ex) {
241 return;
242 } catch (ExecutionException ex) {
243 if (JConsole.isDebug()) {
244 ex.printStackTrace();
245 }
246 return;
247 }
248
249 threadMeter.plotter.addValues(timeStamp, tlCount, tpCount);
250 threadMeter.setValueLabel(tlCount+"");
251
252 if (overviewPanel != null) {
253 overviewPanel.updateThreadsInfo(tlCount, tpCount, ttCount, timeStamp);
254 }
255
256 String filter = filterTF.getText().toLowerCase(Locale.ENGLISH);
257 boolean doFilter = (filter.length() > 0);
258
259 ArrayList<Long> l = new ArrayList<Long>();
260 for (long t : threads) {
261 l.add(t);
262 }
263 Iterator<Long> iterator = l.iterator();
264 while (iterator.hasNext()) {
265 long newThread = iterator.next();
266 String name = nameCache.get(newThread);
267 if (doFilter && name != null &&
268 name.toLowerCase(Locale.ENGLISH).indexOf(filter) < 0) {
269
270 iterator.remove();
271 }
272 }
273 long[] newThreads = threads;
274 if (l.size() < threads.length) {
275 newThreads = new long[l.size()];
276 for (int i = 0; i < newThreads.length; i++) {
277 newThreads[i] = l.get(i);
278 }
279 }
280
281
282 for (long oldThread : oldThreads) {
283 boolean found = false;
284 for (long newThread : newThreads) {
285 if (newThread == oldThread) {
286 found = true;
287 break;
288 }
289 }
290 if (!found) {
291 listModel.removeElement(oldThread);
292 if (!doFilter) {
293 nameCache.remove(oldThread);
294 }
295 }
296 }
297
298
299 for (int i = newThreads.length - 1; i >= 0; i--) {
300 long newThread = newThreads[i];
301 boolean found = false;
302 for (long oldThread : oldThreads) {
303 if (newThread == oldThread) {
304 found = true;
305 break;
306 }
307 }
308 if (!found) {
309 listModel.addElement(newThread);
310 }
311 }
312 oldThreads = newThreads;
313 }
314 };
315 }
316
317 long lastSelected = -1;
318
319 public void valueChanged(ListSelectionEvent ev) {
320 ThreadJList list = (ThreadJList)ev.getSource();
321 final JTextArea textArea = list.textArea;
322
323 Long selected = (Long)list.getSelectedValue();
324 if (selected == null) {
325 if (lastSelected != -1) {
326 selected = lastSelected;
327 }
328 } else {
329 lastSelected = selected;
330 }
331 textArea.setText("");
332 if (selected != null) {
333 final long threadID = selected;
334 workerAdd(new Runnable() {
335 public void run() {
336 ProxyClient proxyClient = vmPanel.getProxyClient();
337 StringBuilder sb = new StringBuilder();
338 try {
339 ThreadMXBean threadMBean = proxyClient.getThreadMXBean();
340 ThreadInfo ti = null;
341 MonitorInfo[] monitors = null;
342 if (proxyClient.isLockUsageSupported() &&
343 threadMBean.isObjectMonitorUsageSupported()) {
344
345 ThreadInfo[] infos = threadMBean.dumpAllThreads(true, false);
346 for (ThreadInfo info : infos) {
347 if (info.getThreadId() == threadID) {
348 ti = info;
349 monitors = info.getLockedMonitors();
350 break;
351 }
352 }
353 } else {
354
355 ti = threadMBean.getThreadInfo(threadID, Integer.MAX_VALUE);
356 }
357 if (ti != null) {
358 if (ti.getLockName() == null) {
359 sb.append(Resources.getText("Name State",
360 ti.getThreadName(),
361 ti.getThreadState().toString()));
362 } else if (ti.getLockOwnerName() == null) {
363 sb.append(Resources.getText("Name State LockName",
364 ti.getThreadName(),
365 ti.getThreadState().toString(),
366 ti.getLockName()));
367 } else {
368 sb.append(Resources.getText("Name State LockName LockOwner",
369 ti.getThreadName(),
370 ti.getThreadState().toString(),
371 ti.getLockName(),
372 ti.getLockOwnerName()));
373 }
374 sb.append(Resources.getText("BlockedCount WaitedCount",
375 ti.getBlockedCount(),
376 ti.getWaitedCount()));
377 sb.append(Resources.getText("Stack trace"));
378 int index = 0;
379 for (StackTraceElement e : ti.getStackTrace()) {
380 sb.append(e.toString()+"\n");
381 if (monitors != null) {
382 for (MonitorInfo mi : monitors) {
383 if (mi.getLockedStackDepth() == index) {
384 sb.append(Resources.getText("Monitor locked", mi.toString()));
385 }
386 }
387 }
388 index++;
389 }
390 }
391 } catch (IOException ex) {
392
393 } catch (UndeclaredThrowableException e) {
394 proxyClient.markAsDead();
395 }
396 final String text = sb.toString();
397 SwingUtilities.invokeLater(new Runnable() {
398 public void run() {
399 textArea.setText(text);
400 textArea.setCaretPosition(0);
401 }
402 });
403 }
404 });
405 }
406 }
407
408 private void doUpdate() {
409 workerAdd(new Runnable() {
410 public void run() {
411 update();
412 }
413 });
414 }
415
416
417 private void detectDeadlock() {
418 workerAdd(new Runnable() {
419 public void run() {
420 try {
421 final Long[][] deadlockedThreads = getDeadlockedThreadIds();
422
423 if (deadlockedThreads == null || deadlockedThreads.length == 0) {
424
425
426
427 new Thread() {
428 public void run() {
429 try {
430 SwingUtilities.invokeAndWait(new Runnable() {
431 public void run() {
432 String msg = Resources.getText("No deadlock detected");
433 messageLabel.setText(msg);
434 threadListTabbedPane.revalidate();
435 }
436 });
437 sleep(30 * 1000);
438 } catch (InterruptedException ex) {
439
440 } catch (InvocationTargetException ex) {
441
442 }
443 SwingUtilities.invokeLater(new Runnable() {
444 public void run() {
445 messageLabel.setText("");
446 }
447 });
448 }
449 }.start();
450 return;
451 }
452
453 SwingUtilities.invokeLater(new Runnable() {
454 public void run() {
455
456 while (threadListTabbedPane.getTabCount() > 1) {
457 threadListTabbedPane.removeTabAt(1);
458 }
459
460 if (deadlockedThreads != null) {
461 for (int i = 0; i < deadlockedThreads.length; i++) {
462 DefaultListModel listModel = new DefaultListModel();
463 JTextArea textArea = new JTextArea();
464 textArea.setBorder(thinEmptyBorder);
465 textArea.setEditable(false);
466 setAccessibleName(textArea,
467 getText("ThreadTab.threadInfo.accessibleName"));
468 JList list = new ThreadJList(listModel, textArea);
469 JScrollPane threadlistSP = new JScrollPane(list);
470 JScrollPane textAreaSP = new JScrollPane(textArea);
471 threadlistSP.setBorder(null);
472 textAreaSP.setBorder(null);
473 JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
474 threadlistSP, textAreaSP);
475 splitPane.setOneTouchExpandable(true);
476 splitPane.setBorder(null);
477 splitPane.setDividerLocation(threadsSplitPane.getDividerLocation());
478 String tabName;
479 if (deadlockedThreads.length > 1) {
480 tabName = Resources.getText("deadlockTabN", i+1);
481 } else {
482 tabName = Resources.getText("deadlockTab");
483 }
484 threadListTabbedPane.addTab(tabName, splitPane);
485
486 for (long t : deadlockedThreads[i]) {
487 listModel.addElement(t);
488 }
489 }
490 threadListTabbedPane.setSelectedIndex(1);
491 }
492 }
493 });
494 } catch (IOException e) {
495
496 } catch (UndeclaredThrowableException e) {
497 vmPanel.getProxyClient().markAsDead();
498 }
499 }
500 });
501 }
502
503
504
505 public Long[][] getDeadlockedThreadIds() throws IOException {
506 ProxyClient proxyClient = vmPanel.getProxyClient();
507 ThreadMXBean threadMBean = proxyClient.getThreadMXBean();
508
509 long[] ids = proxyClient.findDeadlockedThreads();
510 if (ids == null) {
511 return null;
512 }
513 ThreadInfo[] infos = threadMBean.getThreadInfo(ids, Integer.MAX_VALUE);
514
515 List<Long[]> dcycles = new ArrayList<Long[]>();
516 List<Long> cycle = new ArrayList<Long>();
517
518
519
520 boolean[] visited = new boolean[ids.length];
521
522 int deadlockedThread = -1;
523 while (true) {
524 if (deadlockedThread < 0) {
525 if (cycle.size() > 0) {
526
527 dcycles.add(cycle.toArray(new Long[0]));
528 cycle = new ArrayList<Long>();
529 }
530
531 for (int j = 0; j < ids.length; j++) {
532 if (!visited[j]) {
533 deadlockedThread = j;
534 visited[j] = true;
535 break;
536 }
537 }
538 if (deadlockedThread < 0) {
539
540 break;
541 }
542 }
543
544 cycle.add(ids[deadlockedThread]);
545 long nextThreadId = infos[deadlockedThread].getLockOwnerId();
546 for (int j = 0; j < ids.length; j++) {
547 ThreadInfo ti = infos[j];
548 if (ti.getThreadId() == nextThreadId) {
549 if (visited[j]) {
550 deadlockedThread = -1;
551 } else {
552 deadlockedThread = j;
553 visited[j] = true;
554 }
555 break;
556 }
557 }
558 }
559 return dcycles.toArray(new Long[0][0]);
560 }
561
562
563
564
565
566
567 public void actionPerformed(ActionEvent evt) {
568 String cmd = ((AbstractButton)evt.getSource()).getActionCommand();
569
570 if (cmd == "detectDeadlock") {
571 messageLabel.setText("");
572 detectDeadlock();
573 }
574 }
575
576
577
578
579
580 public void insertUpdate(DocumentEvent e) {
581 doUpdate();
582 }
583
584 public void removeUpdate(DocumentEvent e) {
585 doUpdate();
586 }
587
588 public void changedUpdate(DocumentEvent e) {
589 doUpdate();
590 }
591
592
593
594 private class ThreadJList extends JList {
595 private JTextArea textArea;
596
597 ThreadJList(DefaultListModel listModel, JTextArea textArea) {
598 super(listModel);
599
600 this.textArea = textArea;
601
602 setBorder(thinEmptyBorder);
603
604 addListSelectionListener(ThreadTab.this);
605 setCellRenderer(new DefaultListCellRenderer() {
606 public Component getListCellRendererComponent(JList list, Object value, int index,
607 boolean isSelected, boolean cellHasFocus) {
608 super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
609
610 if (value != null) {
611 String name = nameCache.get(value);
612 if (name == null) {
613 name = value.toString();
614 }
615 setText(name);
616 }
617 return this;
618 }
619 });
620 }
621
622 public Dimension getPreferredSize() {
623 Dimension d = super.getPreferredSize();
624 d.width = Math.max(d.width, 100);
625 return d;
626 }
627 }
628
629 private class PromptingTextField extends JTextField implements FocusListener {
630 private String prompt;
631 boolean promptRemoved = false;
632 Color fg;
633
634 public PromptingTextField(String prompt, int columns) {
635 super(prompt, columns);
636
637 this.prompt = prompt;
638 updateForeground();
639 addFocusListener(this);
640 setAccessibleName(this, prompt);
641 }
642
643 @Override
644 public void revalidate() {
645 super.revalidate();
646 updateForeground();
647 }
648
649 private void updateForeground() {
650 this.fg = UIManager.getColor("TextField.foreground");
651 if (promptRemoved) {
652 setForeground(fg);
653 } else {
654 setForeground(Color.gray);
655 }
656 }
657
658 public String getText() {
659 if (!promptRemoved) {
660 return "";
661 } else {
662 return super.getText();
663 }
664 }
665
666 public void focusGained(FocusEvent e) {
667 if (!promptRemoved) {
668 setText("");
669 setForeground(fg);
670 promptRemoved = true;
671 }
672 }
673
674 public void focusLost(FocusEvent e) {
675 if (promptRemoved && getText().equals("")) {
676 setText(prompt);
677 setForeground(Color.gray);
678 promptRemoved = false;
679 }
680 }
681
682 }
683
684 OverviewPanel[] getOverviewPanels() {
685 if (overviewPanel == null) {
686 overviewPanel = new ThreadOverviewPanel();
687 }
688 return new OverviewPanel[] { overviewPanel };
689 }
690
691
692 private static class ThreadOverviewPanel extends OverviewPanel {
693 ThreadOverviewPanel() {
694 super(getText("Threads"), threadCountKey, threadCountName, null);
695 }
696
697 private void updateThreadsInfo(long tlCount, long tpCount, long ttCount, long timeStamp) {
698 getPlotter().addValues(timeStamp, tlCount);
699 getInfoLabel().setText(getText(infoLabelFormat, tlCount, tpCount, ttCount));
700 }
701 }
702 }